package cn.net.yugu.doraemon.springweb.interceptor;

import cn.net.yugu.doraemon.springweb.config.YgWebProperties;
import cn.net.yugu.doraemon.springweb.constant.YgConstant;
import cn.net.yugu.doraemon.springweb.enums.BaseEnum;
import cn.net.yugu.doraemon.springweb.enums.SignAlgorithmType;
import cn.net.yugu.doraemon.springweb.exception.BusinessException;
import cn.net.yugu.doraemon.springweb.util.ServletUtil;
import cn.net.yugu.doraemon.springweb.util.SignUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @author wanghongli
 * @date 2023/3/21 13:15
 * @description YgHttpAuthCheckInterceptor
 **/
@Slf4j
@Order(999)
public class YgWebSignCheckInterceptor implements HandlerInterceptor {
    private static final String SUPPORT_METHOD = "post";
    private static final String SIGN = "sign";
    private static final String SIGN_TYPE = "signType";
    private static final String SIGN_TIMESTAMP = "timestamp";
    private final YgWebProperties ygWebProperties;

    public YgWebSignCheckInterceptor(YgWebProperties properties) {
        this.ygWebProperties = properties;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        if (!ygWebProperties.isSignEnable()) {
            return true;
        }
        if (!SUPPORT_METHOD.equalsIgnoreCase(request.getMethod())) {
            return true;
        }
        String signType = getRequestSignType(request);
        if (StringUtils.isBlank(signType)) {
            throwSignError("unSupport sign type");
        }
        SignAlgorithmType signAlgorithmType = BaseEnum.getBykey(ygWebProperties.getSignAlgorithmType(), SignAlgorithmType.class);
        if (signAlgorithmType == null) {
            throwSignError("unSupport sign algorithm");
        }
        String reqSign = getRequestSign(request);
        if (StringUtils.isBlank(reqSign)) {
            throwSignError("invalid request sign");
        }
        String reqTimestamp = getRequestTimestamp(request);
        if (StringUtils.isBlank(reqTimestamp)) {
            throwSignError("invalid request missing timestamp");
        }
        long signExpireMills = ygWebProperties.getSignExpireMills() <= 0 ? YgConstant.REQUEST_ACTIVE_MILLS : ygWebProperties.getSignExpireMills();
        if ((System.currentTimeMillis() - Long.parseLong(reqTimestamp)) >= signExpireMills) {
            throwSignError("request sign expired");
        }
        String requestUri = request.getRequestURI();
        int lastIndexOf = requestUri.lastIndexOf('/');
        if (lastIndexOf == -1) {
            throwSignError("invalid request uri");
        }

        List<String> supportOnlyHighSignTypePath = ygWebProperties.getSupportOnlyHighSignTypePath();
        if (!CollectionUtils.isEmpty(supportOnlyHighSignTypePath)) {
            for (String configPath : supportOnlyHighSignTypePath) {
                if (StringUtils.equals(requestUri, configPath) && StringUtils.isNumeric(signType) && signType.length() < 3) {
                    throwSignError("unSupport sign type");
                }
            }
        }

        String uri = requestUri.substring(lastIndexOf + 1);
        String requestBody = ServletUtil.getServletInputStream2String(request);

        if (StringUtils.isNotEmpty(requestBody)) {
            requestBody = Base64.encodeBase64String(requestBody.getBytes(StandardCharsets.UTF_8));
        }

        Map<String, String> param = new HashMap<>();
        param.put("uri", uri);
        param.put("signType", signType);
        param.put("requestBody", requestBody);
        param.put("timestamp", reqTimestamp);
        String signPwd = getSignSecret(signType, ygWebProperties);
        String signStr = SignUtil.sign(param, signPwd, signAlgorithmType);
        if (!reqSign.equalsIgnoreCase(signStr)) {
            throwSignError( "invalid request sign");
        }
        return true;
    }

    private static String getRequestSign(HttpServletRequest request) {
        return StringUtils.isNotBlank(request.getHeader(SIGN)) ? request.getHeader(SIGN) : request.getParameter(SIGN);
    }

    private static String getRequestSignType(HttpServletRequest request) {
        return StringUtils.isNotBlank(request.getHeader(SIGN_TYPE)) ? request.getHeader(SIGN_TYPE) : request.getParameter(SIGN_TYPE);
    }

    private static String getRequestTimestamp(HttpServletRequest request) {
        return StringUtils.isNotBlank(request.getHeader(SIGN_TIMESTAMP)) ? request.getHeader(SIGN_TIMESTAMP) : request.getParameter(SIGN_TIMESTAMP);
    }

    private String getSignSecret(String channelType, YgWebProperties ygWebProperties) {
        return Optional.ofNullable(ygWebProperties.getSignTypeSecretMap()).map(x->x.getOrDefault(channelType, ygWebProperties.getSignSecret())).orElse(ygWebProperties.getSignSecret());
    }

    private static void throwSignError(String errorMsg) {
        throw new BusinessException(YgConstant.SIGN_ERROR_CODE, errorMsg);
    }
}
